package com.lincoln.cloud.serverclient.abstracter;

import com.alibaba.fastjson.JSONObject;
import com.lincoln.cloud.common.bean.CommonServerInfo;
import com.lincoln.cloud.common.bean.ServerMessage;
import com.lincoln.cloud.common.utils.ByteUtil;
import com.lincoln.cloud.common.utils.CommondValue;
import com.lincoln.cloud.serverclient.bean.AbstractMsgNoProtocol;
import com.lincoln.cloud.serverclient.bean.BaseProtocol;
import com.lincoln.cloud.serverclient.exception.ErrorFormatException;
import com.lincoln.cloud.serverclient.interfaces.CommunicationProtocol;
import com.lincoln.cloud.serverclient.interfaces.ServerClientContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

/**
 * CommunicationProtocol的netty实现
 * <p>
 * ServerMessage中的from和to在payload位置:[ServerMessage.from][ServerMessage.to.length][ServerMessage.to..][other payload],其中from为4个字节,to.length,to每个为4个字节
 *
 * @author lincoln
 */
public class NettyCommunicationProtocol implements CommunicationProtocol {
    private static final Logger logger = LoggerFactory.getLogger(NettyCommunicationProtocol.class);

    private static Socket socket;

    @Override
    public boolean init() throws Exception {
        socket = new Socket();
        socket.setSoTimeout(30000);
        try {
            socket.connect(new InetSocketAddress(ServerClientContext.loadProperties().getProperty("center.ip"), Integer.parseInt(ServerClientContext.loadProperties().getProperty("center.port"))));
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    public boolean sendMessage(ServerMessage serverMessage) throws Exception {
        serverMessage.setFrom(ServerClientContext.commonServerInfo().getId());
        socket.getOutputStream().write(serverMessage2protocol(serverMessage).toBytes());
        socket.getOutputStream().flush();
        return true;
    }

    /**
     * AbstractMsgNoProtocol到ServerMessage的转换
     *
     * @param protocol
     * @return
     */
    public static ServerMessage protocol2serverMessage(AbstractMsgNoProtocol protocol) {
        byte[] payload = protocol.getPayload();
        int from = ByteUtil.byte2int(ByteUtil.sub(payload, 0, 4));
        payload = ByteUtil.sub(payload, 4);
        int toLength = ByteUtil.byte2int(ByteUtil.sub(payload, 0, 4));
        payload = ByteUtil.sub(payload, 4);
        int[] tos = new int[toLength];
        for (int i = 0; i < toLength; i++) {
            tos[i] = ByteUtil.byte2int(ByteUtil.sub(payload, 0, 4));
            payload = ByteUtil.sub(payload, 4);
        }
        ServerMessage serverMessage = new ServerMessage();
        serverMessage.setFrom(from);
        serverMessage.setTos(tos);
        serverMessage.setNo(protocol.getNo());
        serverMessage.setCmd(protocol.getCmd());
        serverMessage.setMsg(payload);
        return serverMessage;
    }

    /**
     * byte转serverMessage
     *
     * @param bytes
     * @return
     */
    public static ServerMessage bytes2serverMessage(byte[] bytes) throws ErrorFormatException {
        AbstractMsgNoProtocol protocol = new AbstractMsgNoProtocol(bytes);
        ServerMessage serverMessage = protocol2serverMessage(protocol);
        return serverMessage;
    }

    /**
     * ServerMessage到AbstractMsgNoProtocol的转换
     *
     * @param serverMessage
     * @return 返回的是接收人的那部分payload
     */
    public static AbstractMsgNoProtocol serverMessage2protocol(ServerMessage serverMessage) {
        byte[] res = ByteUtil.append(ByteUtil.int2byte(serverMessage.getFrom()), ByteUtil.int2byte(serverMessage.getTos().length));
        for (int to : serverMessage.getTos()) {
            res = ByteUtil.append(res, ByteUtil.int2byte(to));
        }
        res = ByteUtil.append(res, serverMessage.getMsg());
        AbstractMsgNoProtocol protocol = new AbstractMsgNoProtocol(serverMessage.getNo(), serverMessage.getCmd(), res);
        return protocol;
    }

    @Override
    public boolean login() throws Exception {
        ServerMessage serverMessage = new ServerMessage();
        serverMessage.setCmd(CommondValue.LOGIN_CLIENT_CMD.getBytes());
        serverMessage.setNo(BaseProtocol.getNo(serverMessage.getCmd()));
        serverMessage.setFrom(ServerClientContext.commonServerInfo().getId());
        serverMessage.setTos(new int[]{-1});
        byte[] msg = ByteUtil.append(
                new byte[]{
                        (byte) ServerClientContext.commonServerInfo().getDeviceType(),
                        (byte) ServerClientContext.commonServerInfo().getProtocolType()
                },
                ServerClientContext.loadProperties().getProperty("server-name").getBytes(),
                ByteUtil.int2byte(Integer.parseInt(ServerClientContext.loadProperties().getProperty("port")))
        );
        serverMessage.setMsg(msg);
        sendMessage(serverMessage);

        byte[] bytes = read();
        if (bytes != null) {
            serverMessage = bytes2serverMessage(bytes);
            if (serverMessage.getMsg()[0] == CommondValue.SUCCESS) {
                ServerClientContext.commonServerInfo().setId(ByteUtil.byte2int(ByteUtil.sub(serverMessage.getMsg(), 1)));
                ServerClientContext.commonServerInfo().setFirstContact(new Date());
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    @Override
    public boolean checkHeart() throws Exception {
        ServerMessage serverMessage = new ServerMessage();
        serverMessage.setCmd(CommondValue.HEART_BEAT_CLIENT_CMD.getBytes());
        serverMessage.setNo(BaseProtocol.getNo(serverMessage.getCmd()));
        serverMessage.setFrom(ServerClientContext.commonServerInfo().getId());
        serverMessage.setTos(new int[]{-1});
        serverMessage.setMsg(null);
        sendMessage(serverMessage);
        byte[] bytes = read();
        if (bytes != null) {
            serverMessage = bytes2serverMessage(bytes);
            return serverMessage.getMsg()[0] == CommondValue.SUCCESS;
        } else {
            return false;
        }
    }

    @Override
    public CommonServerInfo achieveServer(String name) throws Exception {
        ServerMessage serverMessage = new ServerMessage();
        serverMessage.setCmd(CommondValue.ACHIEVE_CLIENT_CMD.getBytes());
        serverMessage.setNo(BaseProtocol.getNo(serverMessage.getCmd()));
        serverMessage.setFrom(ServerClientContext.commonServerInfo().getId());
        serverMessage.setTos(new int[]{-1});
        byte[] msg = name.getBytes();
        serverMessage.setMsg(msg);
        sendMessage(serverMessage);
        byte[] bytes = read();
        if (bytes != null) {
            serverMessage = bytes2serverMessage(bytes);
            return JSONObject.parseObject(new String(serverMessage.getMsg()), CommonServerInfo.class);
        } else {
            return null;
        }
    }

    @Override
    public List<CommonServerInfo> achieveServers(String name) throws Exception {
        ServerMessage serverMessage = new ServerMessage();
        serverMessage.setCmd(CommondValue.MUL_ACHIEVE_CLIENT_CMD.getBytes());
        serverMessage.setNo(BaseProtocol.getNo(serverMessage.getCmd()));
        serverMessage.setFrom(ServerClientContext.commonServerInfo().getId());
        serverMessage.setTos(new int[]{-1});
        byte[] msg = name.getBytes();
        serverMessage.setMsg(msg);
        sendMessage(serverMessage);
        byte[] bytes = read();
        if (bytes != null) {
            serverMessage = bytes2serverMessage(bytes);
            return JSONObject.parseArray(new String(serverMessage.getMsg()), CommonServerInfo.class);
        } else {
            return null;
        }
    }


    @Override
    public CommonServerInfo achieveServer(int id) throws Exception {
        ServerMessage serverMessage = new ServerMessage();
        serverMessage.setCmd(CommondValue.ACHIEVE_CLIENT_BY_ID_CMD.getBytes());
        serverMessage.setNo(BaseProtocol.getNo(serverMessage.getCmd()));
        serverMessage.setFrom(ServerClientContext.commonServerInfo().getId());
        serverMessage.setTos(new int[]{-1});
        byte[] msg = ByteUtil.int2byte(id);
        serverMessage.setMsg(msg);
        sendMessage(serverMessage);
        byte[] bytes = read();
        if (bytes != null) {
            serverMessage = bytes2serverMessage(bytes);
            return JSONObject.parseObject(new String(serverMessage.getMsg()), CommonServerInfo.class);
        } else {
            return null;
        }
    }


    @Override
    public List<CommonServerInfo> achieveServers() throws Exception {
        ServerMessage serverMessage = new ServerMessage();
        serverMessage.setCmd(CommondValue.ALL_ACHIEVE_CLIENT_CMD.getBytes());
        serverMessage.setNo(BaseProtocol.getNo(serverMessage.getCmd()));
        serverMessage.setFrom(ServerClientContext.commonServerInfo().getId());
        serverMessage.setTos(new int[]{-1});
        byte[] msg = new byte[0];
        serverMessage.setMsg(msg);
        sendMessage(serverMessage);
        byte[] bytes = read();
        if (bytes != null) {
            serverMessage = bytes2serverMessage(bytes);
            return JSONObject.parseArray(new String(serverMessage.getMsg()), CommonServerInfo.class);
        } else {
            return null;
        }
    }


    @Override
    public boolean updateInfo() throws Exception {
        ServerMessage serverMessage = new ServerMessage();
        serverMessage.setCmd(CommondValue.ALL_ACHIEVE_CLIENT_CMD.getBytes());
        serverMessage.setNo(BaseProtocol.getNo(serverMessage.getCmd()));
        serverMessage.setFrom(ServerClientContext.commonServerInfo().getId());
        serverMessage.setTos(new int[]{-1});
        byte[] msg = JSONObject.toJSONString(ServerClientContext.commonServerInfo()).getBytes();
        serverMessage.setMsg(msg);
        sendMessage(serverMessage);
        byte[] bytes = read();
        if (bytes != null) {
            serverMessage = bytes2serverMessage(bytes);
            return serverMessage.getMsg()[0] == CommondValue.SUCCESS;
        } else {
            return false;
        }
    }

    /**
     * 阻塞读取一次协议格式
     *
     * @return
     * @throws IOException
     */
    private byte[] read() throws Exception {
        try {
            BufferedInputStream in = new BufferedInputStream(socket.getInputStream());
            int r = -1;
            List<Byte> byteList = new LinkedList<>();
            while (true) {
                r = in.read();
                if (byteList.size() < AbstractMsgNoProtocol.HEAD_DATA.length) {
                    if (r != AbstractMsgNoProtocol.HEAD_DATA[byteList.size()]) {
                        byteList.clear();
                        //刚好等于第一个,则重新开始计
                        if (r == AbstractMsgNoProtocol.HEAD_DATA[0]) {
                            byteList.add((byte) r);
                        }
                    } else {
                        byteList.add((byte) r);
                    }
                } else {
                    byteList.add((byte) r);
                    //判断是否又遇到头,遇到则抛到之前接收的
                    boolean check = true;
                    List<Byte> bytes = byteList.subList(0, AbstractMsgNoProtocol.HEAD_DATA.length);
                    for (int i = 0; i < bytes.size(); i++) {
                        if (bytes.get(i).byteValue() != AbstractMsgNoProtocol.TAIL_DATA[i]) {
                            check = false;
                            break;
                        }
                    }
                    if (check) {
                        byteList.clear();
                        continue;
                    }
                    if (byteList.size() > AbstractMsgNoProtocol.HEAD_DATA.length + AbstractMsgNoProtocol.TAIL_DATA.length) {
                        check = true;
                        bytes = byteList.subList(byteList.size() - AbstractMsgNoProtocol.TAIL_DATA.length, byteList.size());
                        for (int i = 0; i < bytes.size(); i++) {
                            if (bytes.get(i).byteValue() != AbstractMsgNoProtocol.TAIL_DATA[i]) {
                                check = false;
                                break;
                            }
                        }
                        if (check) {
                            break;
                        }
                    }
                }
            }
            String log = "";
            byte[] bytes1 = new byte[byteList.size()];
            for (int i = 0; i < byteList.size(); i++) {
                bytes1[i] = byteList.get(i).byteValue();
                log += Integer.toHexString(bytes1[i]) + ",";
            }
            logger.info("收到:" + log);
            return bytes1;
        } catch (IOException e) {
            e.printStackTrace();
            throw new Exception("超时读取");
        }
    }
}
